<?php

/***  DOCUMENTATION LAYER

Klenwell Template Class

Name: Baffle
Version: 0.1
Last Update: Aug 2007
Author: Tom at klenwell@gmail.com

DESCRIPTION
  The main class for bafflegate site metric

METHODS
  MAGIC
  Baffle($debug=0, $oid=null)    *php 4 constructor*
  __construct($debug, $oid)      *php 5 constructor*
  __destruct()  
  
  PUBLIC
  get_client_data()
  select_account_data_by_domain($domain)
  is_ip_blacklisted()
  get_destination_url()
  get_base_domain($url)
  get_subdomain($url)
  get_path($url)
  get_url_domain($url)
  get_query_string($url)
  read_page_title()
  read_referrer_url()
  is_referred_by_seo()
  get_search_keywords()
  get_client_ip()
  get_client_ip_simple()
  get_client_proxy()
  get_client_proxy_ip()
  get_user_agent()
  does_agent_have_js()
  get_agent_os()
  read_agent_os()
  get_agent_browser()
  get_agent_os_from_agent_string($agent_string)
  read_agent_screen_size()
  read_agent_screen_depth()
  is_new_visitor()
  insert_hit()
  insert_session()
  has_active_session()
  has_active_session_cookie()
  set_session_trackers($session_uid)
  select_active_session()
  set_exception($code, $message)
  has_exception()
  insert_exception()
  
  print_d($message, $color='c33')
  debug_dump($dirpath='')
  write_file($content, $fpath, $overwrite=1)
  print_r()
  dump()
  
  PRIVATE
  _load_dbo()
  _get_hostname_by_ip($ip)
  _get_client_proxy_hostname($ip)
  _extract_first_ip($string)
  _is_valid_ip($ip)
  
  _set_session_data()
  _get_session_data()
  _has_session_data()
  
  _set_filename()
  _set_dirpath()
  
USAGE
  $Baffler = new Baffle();


NOTES


______________________________________________________________________________*/

// Load File of Base Class
#$base_fname = 'class.abstract.php';
#$base_dirpath = dirname(__FILE__) . DIRECTORY_SEPARATOR;
#require_once($base_dirpath . $base_fname);


// Baffle
/*____________________________________________________________________________*/
class Baffle
{
/* PUBLIC PROPERTIES */
var $debug = 0;
var $class_name = __CLASS__;
var $oid = '';
var $DS = DIRECTORY_SEPARATOR;

// hard parameters
var $session_length = 3600;    // 1 hour

// encapsulated objects
var $DBO = 0;

// entrance data
var $visitor_baffleticket = '';
var $account_baffleticket = '';
var $account_uid = '';
var $entrance_uts = 0;
var $session_uid = 0;
var $cancel_code = 0;

// client data
var $visiting_url = '';
var $visiting_domain = '';
var $visiting_subdomain = '';
var $visiting_path = '';
var $visiting_qstring = '';
var $visiting_title = '';
var $referrer_url = '';
var $referrer_domain = '';
var $referrer_qstring = '';
var $referred_from_search = '';
var $referring_search_engine = '';
var $referring_keywords = '';
var $query_cue = '';
var $client_ip = '';
var $client_hostname = '';
var $client_proxy = '';
var $client_proxy_ip = '';
var $client_proxy_hostname = '';
var $agent_string = '';
var $agent_hash = '';
var $client_hash = '';
var $agent_browser = '';
var $agent_has_js = 0;
var $agent_os = '';
var $screen_size = '';
var $screen_depth = 0;


/* PRIVATE PROPERTIES */
var $_package_root = '';
var $_config_file = '';
var $_dbo_driver = '';

var $_dbu = '';
var $_dbp = '';
var $_dbn = '';
var $_dbs = '';

var $_EXCEPTION = array();
var $_DEBUG = array();

var $_filename = '';
var $_dirpath = '';

// GET (set in constructor)
var $_GK = array();

// SEO array (set in constructor)
var $_SEO = array();



/* ** MAGIC METHODS ** */
// php4 constructor
function Baffle($debug=0, $oid=null)
{
  $this->__construct($debug, $oid);
  register_shutdown_function( array($this, '__destruct') );
}
// END constructor

// php5 constructor
function __construct($debug=0, $oid=null)
{
  // default
  $this->debug = $debug;
  $this->oid = ( empty($oid) ) ? $this->class_name : $oid;
  $this->_set_filename();
  $this->_set_dirpath();
  
  // set entrance time
  $this->entrance_uts = time();
  
  // GET keys
  $this->_GK['title'] = 'ttl';
  $this->_GK['dst_url'] = 'url';
  $this->_GK['referrer'] = 'ref';
  $this->_GK['has_js'] = 'jsa';
  $this->_GK['agent_os'] = 'aos';
  $this->_GK['screen_size'] = 'ssz';
  $this->_GK['screen_depth'] = 'sdp';
  $this->_GK['is_framed'] = 'frm';
  
  // SEO settings
  $this->_SEO['BOT'] = array('google', 'yahoo', 'ask', 'msn', 'aol', 'feedster', 'lycos', 'altavista', );
  $this->_SEO['CUE'] = array('q', 'as_q', 'MT', 'query', 'p', 's', 'userQuery', 'query');
  
  // drivers
  $this->_package_root = '../';
  $this->_config_file = $this->_package_root . 'config.inc.php';
  $this->_dbo_driver = $this->_package_root . 'oss/adodb_lite/adodb.inc.php';
  
  // includes
  global $CFG;
  require_once($this->_config_file);
  
  // db settings
  $this->_dbu = $CFG['db_user'];
  $this->_dbp = urlencode($CFG['db_pass']);
  $this->_dbn = $CFG['db_name'];
  $this->_dbs = $CFG['db_srvr'];
  
  // objects
  $this->_load_dbo();
  
  // debug
  if ( $this->debug ) $this->print_d('debugging is active for oid ' . $this->oid);
  if ( $this->debug ) $this->print_d('constructor complete for class ' . __CLASS__);
}
// END constructor

// destructor
function __destruct()
{
  if ( $this->debug ) $this->print_d("destroying class {$this->class_name} (oid: {$this->oid})");
}
// END destructor



/* ** PUBLIC METHODS ** */
// method: get_client_data
function get_client_data()
{
  // info about the site visitor visted
  $this->visiting_url = $this->get_destination_url();
  $this->visiting_domain = $this->get_base_domain($this->visiting_url);
  $this->visiting_subdomain = $this->get_subdomain($this->visiting_url);
  $this->visiting_path = $this->get_path($this->visiting_url);
  $this->visiting_qstring = $this->get_query_string($this->visiting_url);
  $this->visiting_title = $this->read_page_title();
  
  // info on where visitor came from (referrer / seo)
  $this->referrer_url = $this->read_referrer_url();
  $this->referrer_domain = $this->get_base_domain($this->referrer_url);
  $this->referrer_qstring = $this->get_query_string($this->referrer_url);
  $this->referred_from_search = $this->is_referred_by_seo();   // if so, will set $this->referring_search_engine 
  $this->referring_keywords = $this->get_search_keywords();
  
  // info on visitor's location (ip/proxy)
  $this->client_ip = $this->get_client_ip();
  $this->client_hostname = $this->_get_hostname_by_ip($this->client_ip);
  $this->client_proxy = $this->get_client_proxy();
  $this->client_proxy_ip = $this->get_client_proxy_ip();
  $this->client_proxy_hostname = $this->_get_client_proxy_hostname($this->client_proxy_ip);
  
  // info on visitor's browser (agent)
  $this->agent_string = $this->get_user_agent();
  $this->agent_hash = md5($this->agent_string);
  $this->client_hash = md5($this->client_ip . $this->agent_hash);
  $this->agent_browser = $this->get_agent_browser(); 
  $this->agent_has_js = $this->does_agent_have_js();
  $this->agent_os = $this->get_agent_os();
  $this->screen_size = $this->read_agent_screen_size();
  $this->screen_depth = $this->read_agent_screen_depth();

  return;
}
// END method


// method: select account data from database (by domain)
function select_account_data_by_domain($domain)
{
  $_table = 'account';
  
  $_sql = <<<SQL
SELECT uid, baffleticket
FROM $_table
WHERE domain = '$domain'
SQL;

  if ( $this->debug ) $this->print_d("attempting to select_account_data_by_domain({$domain})");

  if ( !$DBOr = $this->DBO->Execute($_sql) )
  {
    trigger_error($this->DBO->ErrorMsg(), E_USER_WARNING);
    return 0;
  }
             
  // Check Result Set
  if ( !$DBOr->EOF )
  {
    $ROWS = $DBOr->GetArray();
    $this->account_baffleticket = $ROWS[0]['baffleticket'];
    $this->account_uid = $ROWS[0]['uid'];
  }
  else 
  {
    return 0;
  }

  if ( $this->debug ) $this->print_d("account_baffleticket for {$domain}: {$this->account_baffleticket}");
  
  return 1;
}
// END method


// method: is ip blacklisted?
function is_ip_blacklisted()
{
  $this->print_d(__FUNCTION__ . ' in dev');
}
// END method


// method: get destination url
function get_destination_url()
{
  $url = '';
  
  // first try HTTP_REFERER as this should be the site hosting the tracker
  if ( !empty($_SERVER['HTTP_REFERER']) ) $url = $_SERVER['HTTP_REFERER'];
  
  // fallback, $_GET[key] (if set)
  elseif ( !empty($_GET[$this->_GK['dst_url']]) ) $url = $_GET[$this->_GK['dst_url']];
  
  return $url;
}
// END method


// method: get base domain from url
function get_base_domain($url)
{
  $base_domain = '';
  
  if ( $this->debug ) $this->print_d("get_base_domain for url: $url");
  
  // generic tlds (source: http://en.wikipedia.org/wiki/Generic_top-level_domain)
  $G_TLD = array(
    'biz','com','edu','gov','info','int','mil','name','net','org',
    'aero','asia','cat','coop','jobs','mobi','museum','pro','tel','travel',
    'arpa','root',
    'berlin','bzh','cym','gal','geo','kid','kids','lat','mail','nyc','post','sco','web','xxx',
    'nato',
    'example','invalid','localhost','test',
    'bitnet','csnet','ip','local','onion','uucp',
    'co'   // note: not technically, but used in things like co.uk
  );
  
  // country tlds (source: http://en.wikipedia.org/wiki/Country_code_top-level_domain)
  $C_TLD = array(
    // active
    'ac','ad','ae','af','ag','ai','al','am','an','ao','aq','ar','as','at','au','aw','ax','az',
    'ba','bb','bd','be','bf','bg','bh','bi','bj','bm','bn','bo','br','bs','bt','bw','by','bz',
    'ca','cc','cd','cf','cg','ch','ci','ck','cl','cm','cn','co','cr','cu','cv','cx','cy','cz',
    'de','dj','dk','dm','do','dz','ec','ee','eg','er','es','et','eu','fi','fj','fk','fm','fo',
    'fr','ga','gd','ge','gf','gg','gh','gi','gl','gm','gn','gp','gq','gr','gs','gt','gu','gw',
    'gy','hk','hm','hn','hr','ht','hu','id','ie','il','im','in','io','iq','ir','is','it','je',
    'jm','jo','jp','ke','kg','kh','ki','km','kn','kr','kw','ky','kz','la','lb','lc','li','lk',
    'lr','ls','lt','lu','lv','ly','ma','mc','md','mg','mh','mk','ml','mm','mn','mo','mp','mq',
    'mr','ms','mt','mu','mv','mw','mx','my','mz','na','nc','ne','nf','ng','ni','nl','no','np',
    'nr','nu','nz','om','pa','pe','pf','pg','ph','pk','pl','pn','pr','ps','pt','pw','py','qa',
    're','ro','ru','rw','sa','sb','sc','sd','se','sg','sh','si','sk','sl','sm','sn','sr','st',
    'sv','sy','sz','tc','td','tf','tg','th','tj','tk','tl','tm','tn','to','tr','tt','tv','tw',
    'tz','ua','ug','uk','us','uy','uz','va','vc','ve','vg','vi','vn','vu','wf','ws','ye','yu',
    'za','zm','zw',
    // inactive
    'eh','kp','me','rs','um','bv','gb','pm','sj','so','yt','su','tp','bu','cs','dd','zr'
    );
  
  
  // get domain
  if ( !$full_domain = $this->get_url_domain($url) )
  {
    return $base_domain;
  }
  
  // now the fun
  
    // break up domain, reverse
    $DOMAIN = explode('.', $full_domain);
    $DOMAIN = array_reverse($DOMAIN);
    if ( $this->debug ) $this->print_d('get_base_domain() -> DOMAIN: ' . $this->print_r($DOMAIN));
    
    // first check for ip address
    if ( count($DOMAIN) == 4 && is_numeric($DOMAIN[0]) && is_numeric($DOMAIN[3]) )
    {
      return $full_domain;
    }
    
    // if only 2 domain parts, that must be our domain
    if ( count($DOMAIN) <= 2 ) return $full_domain;
    
    // for explanation of logic, see: http://bafflegate.googlecode.com/svn/trunk/version0/functions.m02.inc.php
    if ( in_array($DOMAIN[0], $C_TLD) && in_array($DOMAIN[1], $G_TLD) && strtolower($DOMAIN[2]) != 'www' )
    {
      $base_domain = $DOMAIN[2] . '.' . $DOMAIN[1] . '.' . $DOMAIN[0];
    }
    else
    {
      $base_domain = $DOMAIN[1] . '.' . $DOMAIN[0];;
    }
  
  // did we succeed?  
  return $base_domain;
}
// END method


// method: get subdomain
function get_subdomain()
{
  $subdomain = '';
  if ( !$full_domain = $this->get_url_domain($url) ) return '';
  if ( !$base_domain = $this->get_base_domain($url) ) return '';
  $subdomain = str_replace($base_domain, '', $full_domain);
  if ( substr($subdomain,-1) == '.' ) $subdomain = substr($subdomain,0,strlen($subdomain)-1);
  return $subdomain;
}
// END method


// method: get path (url - domain & qstring)
function get_path($url)
{
  $path = '';
  $_URL = parse_url($url);
  if ( !empty($_URL) || !empty($_URL['path']) ) $path = $_URL['path'];
  return $path;
}
// END method


// method: get url domain
function get_url_domain($url) 
{
  $domain = '';
  
  $_URL = parse_url($url);
  
  // sanity check
  if ( empty($_URL) || empty($_URL['host']) )
  {
    $domain = '';
  }
  else
  {
    $domain = $_URL['host'];
  }
  
  return $domain;
} 
// END method

// method: get query string
function get_query_string($url)
{
  $qstring = '';
  $delim_pos = strpos($url, '?');
  if ( $delim_pos === FALSE ) return ''; 
  $qstring = substr($url, $delim_pos + 1);
  
  // if query strings are separated by colons, change them to '&'
  if ( strpos($qstring,';') !== FALSE && strpos($qstring,'&') === FALSE ) $qstring = str_replace(';','&',$qstring);
  
  return $qstring;
}
// END method


// method: get page title
function read_page_title()
{
  $page_title = '';
  if ( !empty($_GET[$this->_GK['title']]) ) $page_title = urldecode($_GET[$this->_GK['title']]);
  return $page_title;
}
// END method


// method: get referring url (remember: this is actually added to referring url in js file)
function read_referrer_url()
{
  $url = '';
  if ( !empty($_GET[$this->_GK['referrer']]) ) $url = urldecode($_GET[$this->_GK['referrer']]);
  return $url;
}
// END method


// method: has this visitor been referred by a search engine?
function is_referred_by_seo()
{
  $is_referred = 0;
  if ( empty($this->referrer_qstring) ) return 0;
  
  // look for 
  foreach ( $this->_SEO['BOT'] as $_bot )
  {
    if ( strpos(strtolower($this->referrer_domain),strtolower($_bot)) !== FALSE )
    {
      $is_referred = 1;
      $this->referring_search_engine = $_bot;
      break;
    }
  }
  
  return $is_referred;
}
// END method


// method: get search words
function get_search_keywords()
{
  $keywords = '';
  
  $_KEYWORDS = array();
  $_QSTR = explode('&',$this->referrer_qstring);
  foreach ( $_QSTR as $_qpair )
  {
    $_Q = explode('=',$_qpair);
    if ( in_array($_Q[0], $this->_SEO['CUE']) ) $_KEYWORDS[] = array( $_Q[0], trim($_Q[1]));
  }
  
  // make sure we didn't find conflicting query cues
  if ( !empty($_KEYWORDS) )
  {
    $_maxlen = 0;
    foreach ( $_KEYWORDS as $_Q )
    {
      $_termlen = strlen($_Q[1]);
      if ( $_termlen > $_maxlen )
      {
        $_maxlen = $_termlen;
        $keywords = $_Q[1];
        $this->query_cue = $_Q[0];
      }
    }
  }
  
  $keywords = str_replace('+', ' ', $keywords);
  
  return $keywords;
}
// END method


// method: get client's ip
function get_client_ip() 
{
  $ip = '';
  
  // first we try HTTP_CLIENT_IP
  if ( !empty($_SERVER['HTTP_CLIENT_IP']) )
  {
    $ip = $this->_extract_first_ip($_SERVER['HTTP_CLIENT_IP']);
  }
  
  // then HTTP_X_FORWARDED
  elseif ( !empty($_SERVER['HTTP_X_FORWARDED']) )
  {
    $ip = $this->_extract_first_ip($_SERVER['HTTP_X_FORWARDED']);
  }
  
  // then HTTP_X_FORWARDED_FOR
  elseif ( !empty($_SERVER['HTTP_X_FORWARDED_FOR']) )
  {
    $ip = $this->_extract_first_ip($_SERVER['HTTP_X_FORWARDED_FOR']);
  }
  
  // default: REMOTE_ADDR
  if ( !$this->_is_valid_ip($ip) )
  {
    $ip = $this->get_client_ip_simple();
  }

  return $ip;
} 
// END method


// method: get client ip
function get_client_ip_simple() 
{
  $ip = '';
  if ( !empty($_SERVER['REMOTE_ADDR']) ) $ip = $_SERVER['REMOTE_ADDR'];
  return $ip;
} 
// END method


// method: get client proxy
function get_client_proxy()
{
  $proxy = '';
  
  if ( empty($_SERVER['HTTP_VIA']) ) return false;
  
  $proxy = trim(addslashes(urldecode(strstr($_SERVER['HTTP_VIA'],' '))));
  return $proxy;
}
// END method


// method: get client proxy ip
function get_client_proxy_ip()
{
  $proxy_ip = '';
  if ( empty($this->client_proxy) ) return '';
  if ( !$proxy_ip = get_client_ip_simple() ) $proxy_ip = $this->client_ip;
}
// END method


// method: get user agent string
function get_user_agent()
{
  return  $_SERVER['HTTP_USER_AGENT'];
}
// END method


// method: agent has js enabled
function does_agent_have_js()
{
  $is_enabled = 0;
  if ( !isset($_GET[$this->_GK['has_js']]) ) return -1;
  $is_enabled = $_GET[$this->_GK['has_js']];
  return $is_enabled;
}
// END method


// method: get agent os
function get_agent_os()
{
  $agent_os = '';
  if ( $agent_os = $this->read_agent_os() ) return $agent_os;
  $agent_os = $this->get_agent_os_from_agent_string($this->agent_string);
  return $agent_os;
}
// END method


// method: read agent os (from js var)
function read_agent_os()
{
  $agent_os = false;
  if ( !empty($_GET[$this->_GK['agent_os']]) ) $agent_os = urldecode($_GET[$this->_GK['agent_os']]);
  return $agent_os;
}
// END method


// method: get agent browser
function get_agent_browser()
{
  $browser = '';
  /* a simple way to do this?  
    browscap is heavy, old pphlogger a little more reasonable, but still pretty kludgy
    goal: id top 10 browsers, set all others to other (you'll have the agent string for post-mortems)
    maybe best to just leave it to post-mortems, you could always use browscap for that -
    that way, your just calling it for occasional reports, rather than every log
  */
  return $browser;
}
// END method


// method: get agent os from agent string
function get_agent_os_from_agent_string($agent_string)
{
  $agent_os = '';

  // let's target the major one and add as we get back data
  $OS = array
  (
    'ms' => 'win',
    'mac' => array('mac','ppc'),
    'linux' => 'linux',
    'bsd' => 'bsd',
    'sun' => 'sun',
    'psp' => 'psp',
  );
  
  if ( empty($agent_string) ) return '';
  $agent_string = strtolower($agent_string);
  $agent_os = 'unrecognized';
  $this->print_d($agent_string);
  
  // look for our os
  $_FLAG['os_id'] = 0;
  foreach ( $OS as $_key => $_OS_ID )
  {
    if ( !is_array($_OS_ID) ) $_OS_ID = array( $_OS_ID );
    foreach ( $_OS_ID as $_o )
    {
      if ( strpos($agent_string, $_o) !== FALSE ) 
      {
        $agent_os = $_key;
        $_FLAG['os_id'] = 1;
        break;
      }
    }
    if ( $_FLAG['os_id'] ) break;
  }
  
  return $agent_os;
}
// END method


// method: read agent screen size (from js var)
function read_agent_screen_size()
{
  $screen_size = false;
  if ( !empty($_GET[$this->_GK['screen_size']]) ) $screen_size = urldecode($_GET[$this->_GK['screen_size']]);
  return $screen_size;
}
// END method


// method: read agent screen depth (from js var)
function read_agent_screen_depth()
{
  $screen_depth = false;
  if ( !empty($_GET[$this->_GK['screen_depth']]) ) $screen_depth = urldecode($_GET[$this->_GK['screen_depth']]);
  return $screen_depth;
}
// END method


// method: is this a new visitor or does a session already exist?
function is_new_visitor()
{
  $is_new = 0;
  if ( $this->debug ) $this->print_d(__FUNCTION__ . ': looking for an existing session');
  
  // active session?
  if ( $this->has_active_session() )
  {
    $this->session_uid = $_SESSION[$this->oid]['session_uid'];
  }
  
  // browser cookie?
  elseif ( $this->has_active_session_cookie() )
  {
    $this->session_uid = $_COOKIE[$this->account_baffleticket]['session_uid'];
  }
  
  // recently logged in db?
  elseif ( $db_session_uid = $this->select_active_session() )
  {
    $this->session_uid = $db_session_uid;
  }
  
  // new
  else
  {
    $this->session_uid = 0;
    $is_new = 1;
  }
  
  if ( $this->debug ) $this->print_d(__FUNCTION__ . " -> is_new / session_uid : $is_new / {$this->session_uid}");
  
  return $is_new;
}
// END method


// method: insert hit in DB
function insert_hit($session_uid=0)
{
  $_sql = '';
  $_table = TBL_HIT;
  
  if ( $this->debug ) $this->print_d('inserting hit into db');

  // prepare data
  if ( !$_php_sid = session_id() ) $_php_sid = '?';
  
  // bind data
  $datetime = date('Y-m-d h:i:s', $this->entrance_uts);
  
  $_DATA_BIND = array
  (
    $session_uid,  // session_uid
    $this->visitor_baffleticket,  // baffleticket
    $this->cancel_code,  // cancel_code
    $this->visiting_url,  // url
    $this->visiting_domain,  // domain

    $this->visiting_subdomain,  // subdomain
    $this->visiting_path,  // path
    $this->visiting_qstring,  // query_string
    $this->referrer_url,  // referrer_url
    $datetime,  // datetime
    
    $this->entrance_uts,  // uts
  );
  
  $_sql .= <<<SQL
INSERT INTO `$_table` ( 
  session_uid, baffleticket, cancel_code, url, domain,
  subdomain, path, query_string, referrer_url, datetime, 
  uts
) 
VALUES ( ?,?,?,?,?, ?,?,?,?,?, ? )

SQL;
  
  if ( $this->debug ) 
  {
    $_bind_dump = print_r($_DATA_BIND, 1);
    $this->print_d("<pre>SQL:\n{$_sql}\n\nDATA BIND:{$_bind_dump}</pre>");
  }
  
  // Run Query
  if ( !$DBO_r = $this->DBO->Execute($_sql,$_DATA_BIND) )
  {
    trigger_error($DBO->ErrorMsg(), E_USER_WARNING);
    return 0;
  }
  else
  {
    $insert_id = $this->DBO->Insert_ID();
    if ( empty($insert_id) )
    {
      if ( $this->debug ) trigger_error('insert successful, but no insert id -> setting to -1 to avoid error', E_USER_NOTICE);
      $insert_id = -1;
    }
  }
  
  return $insert_id; 

}
// END method


// method: insert session in DB
function insert_session()
{
  $_sql = '';
  $_table = TBL_SESSION;
  
  if ( $this->debug ) $this->print_d('inserting session into db');

  // prepare data
  if ( !$_php_sid = session_id() ) $_php_sid = '?';
  
  // bind data  
  $_DATA_BIND = array
  (
    $_php_sid,  // php_sid
    $this->account_uid,  // account_uid
    $this->referrer_url,  // referrer_url
    $this->referrer_domain,  // referrer_domain
    $this->referrer_qstring,  // referrer_qstring
    
    $this->referred_from_search,  // is_search
    $this->referring_search_engine,  // search_engine
    $this->referring_keywords,  // keywords
    $this->query_cue,  // query_cue
    $this->client_ip,  // client_ip
    
    $this->client_hostname,  // client_host
    $this->client_proxy,  // proxy
    $this->client_proxy_ip,  // proxy_ip
    $this->client_proxy_hostname,  // proxy_host
    $this->client_hash,  // client_hash
    
    $this->agent_string,  // agent_string
    $this->agent_hash,  // agent_hash
    $this->agent_os,  // agent_os    
    $this->screen_size,  // screen_size    
    $this->screen_depth,  // screen_depth
    
    $this->entrance_uts,  // uts
  );
  
  $_sql .= <<<SQL
INSERT INTO `$_table` ( 
  php_sid, account_uid, referrer_url, referrer_domain, referrer_qstring,
  is_search, search_engine, keywords, query_cue, client_ip,
  client_host, proxy, proxy_ip, proxy_host, client_hash,
  agent_string, agent_hash, agent_os, screen_size, screen_depth,
  uts
) 
VALUES ( ?,?,?,?,?, ?,?,?,?,?, ?,?,?,?,?, ?,?,?,?,?, ? )

SQL;
  
  if ( $this->debug ) 
  {
    $_bind_dump = print_r($_DATA_BIND, 1);
    $this->print_d("<pre>SQL:\n{$_sql}\n\nDATA BIND:{$_bind_dump}</pre>");
  }
  
  // Run Query
  if ( !$DBO_r = $this->DBO->Execute($_sql,$_DATA_BIND) )
  {
    trigger_error($DBO->ErrorMsg(), E_USER_WARNING);
    return 0;
  }
  else
  {
    $insert_id = $this->DBO->Insert_ID();
    if ( empty($insert_id) )
    {
      if ( $this->debug ) trigger_error('insert successful, but no insert id -> setting to -1 to avoid error', E_USER_NOTICE);
      $insert_id = -1;
    }
  }
  
  return $insert_id; 
}
// END method


// method: visitor has active session?
function has_active_session()
{
  $has_session = 0;
  
  if ( !empty($_SESSION[$this->account_baffleticket]['session_uid']) 
    && ($this->entrance_uts - $_SESSION[$this->account_baffleticket]['start_uts']) < $this->session_length )
  {
    $has_session = $_SESSION[$this->account_baffleticket]['session_uid'];
  }
  
  if ( $this->debug ) $this->print_d(__FUNCTION__ . '-> SESSION session: ' . $has_session);

  return $has_session;
}
// END method


// method: visitor has browser cookie?
function has_active_session_cookie()
{
  $has_session = 0;
  if ( !empty($_COOKIE[$this->account_baffleticket]['session_uid']) 
     && ($this->entrance_uts - $_COOKIE[$this->account_baffleticket]['start_uts']) < $this->session_length )
  {
    $has_session = $_COOKIE[$this->account_baffleticket]['session_uid'];
  }
  
  if ( $this->debug ) $this->print_d(__FUNCTION__ . '-> COOKIE session: ' . $has_session);

  return $has_session;
}
// END method


// method: set session tracking cookies
function set_session_trackers($session_uid)
{
  // set SESSION values
  $_SESSION[$this->account_baffleticket]['session_uid'] = $session_uid;
  $_SESSION[$this->account_baffleticket]['start_uts'] = time();
  
  // set COOKIEs
  setcookie("{$this->account_baffleticket}[session_uid]", $session_uid, time()+$this->session_length);
  setcookie("{$this->account_baffleticket}[start_uts]", time(), time()+$this->session_length); 
  
  if ( $this->debug ) $this->print_d(__FUNCTION__ . '-> COOKIE and SESSION values have been set');

  return;
}
// END method


// method: has this visitor been recently logged in the database?
function select_active_session()
{
  $db_session_uid = 0;
  $_table = 'session';
  
  if ( $this->debug ) $this->print_d(__FUNCTION__ . '-> looking for DB session');
  
  $_sql = <<<SQL
SELECT uid,	client_ip, proxy
FROM $_table
WHERE agent_hash = '{$this->agent_hash}'
  AND ({$this->entrance_uts} - uts) < {$this->session_length}
SQL;

  if ( !$DBOr = $this->DBO->Execute($_sql) )
  {
    trigger_error($this->DBO->ErrorMsg(), E_USER_WARNING);
    return 0;
  }
             
  // Check Result Set
  if ( !$DBOr->EOF ) $ROWS = $DBOr->GetArray();
  else return 0;
  
  // look for a match among results
  foreach ( $ROWS as $_ROW )
  {
    // matching client ip
    if ( $_ROW['client_ip'] == $this->client_ip )
    {
      $db_session_uid = $_ROW['uid'];
      break;
    }
    
    // matching proxy
    elseif ( !empty($_ROW['proxy']) && $_ROW['proxy'] == $this->proxy )
    {
      $db_session_uid = $_ROW['uid'];
      break;
    }
  }

  if ( $this->debug ) $this->print_d(__FUNCTION__ . '-> DB session : ' . $db_session_uid);
  
  return $db_session_uid;
}
// END method


// method: set exception
function set_exception($code, $message)
{
  $this->print_d("setting exception ($code): $message");
  $this->_EXCEPTION[] = array($code, $message);
}
// END method


// method: set exception
function has_exception()
{
  $has = 0;
  if ( !empty($this->_EXCEPTION) ) $has = 1;
  return $has;
}
// END method


// method: insert exceptions in DB
function insert_exception()
{
  $_sql = '';
  $_table = 'exception';
  
  if ( $this->debug ) $this->print_d('inserting exceptions into db');
  
  // do we have any exceptions?
  if ( empty($this->_EXCEPTION) ) return;
  
  foreach ( $this->_EXCEPTION as $_XDATA )
  {
    $_DATA_BIND[] = array($_XDATA[0],$this->visiting_url,$this->referrer_url,$this->referrer_domain,$_XDATA[1],$this->entrance_uts);
  }
  
  $_sql .= <<<SQL
INSERT INTO `$_table` 
( code, url, referrer_url, referrer_domain, message, uts ) 
VALUES ( ?,?,?,?,?,? )

SQL;
  
  if ( $this->debug ) $this->print_d("<pre>SQL:\n{$_sql}</pre>");
  
  // Run Query
  if ( !$DBO_r = $this->DBO->Execute($_sql,$_DATA_BIND) )
  {
    trigger_error($DBO->ErrorMsg(), E_USER_WARNING);
    return 0;
  }
  else
  {
    $insert_id = $this->DBO->Insert_ID();
    if ( empty($insert_id) )
    {
      trigger_error('insert successful, but no insert id -> setting to -1 to avoid error', E_USER_NOTICE);
      $insert_id = -1;
    }
    $this->_EXCEPTION = array();      // reset
  }
  
  return $insert_id;  
}
// END method


// method: print_d
function print_d($message, $color='#c33')
{
  $out = "<div style='line-height:1.5em; font-family:monospace; color:$color;'>$message</div>";
  $this->_DEBUG[] = $message;
  echo $out;
  return;
}
// END method


// method: print_d
function debug_dump($dirpath='')
{
  $_output = '';
  $_d1 = date('Ymdhi');
  $_r = date('r');
  
  if ( empty($dirpath) ) $dirpath = './';
  $fname = $this->class_name . ".debug.{$_d1}.txt";
  $fpath = $dirpath . $fname;
  
  if ( $this->debug ) $this->print_d("debug dump to file [$fname] (don't forget to clean out dir [$dirpath])");
  
  $i = 0;
  foreach ( $this->_DEBUG as $_msg ) 
  {
    $i++;
    $si = sprintf('%03s',$i);
    $_msg = strip_tags($_msg);
    $_output .= "[$si] $_msg\n";
  }
  
  // header
  $_header = <<<TXT
bafflegate
debug dump stack ($_r)


TXT;

  $this->write_file($_header.$_output, $fpath, $overwrite=1);
  return;
}
// END method


// method: write to file
function write_file($content, $fpath, $overwrite=1)
{
  $is_written = 0;
	$_MODE = array
	(
		'w_start' => 'w',			// overwrite
		'w_end' => 'a',				// append
	);

	// set mode
	$_mode = ( $overwrite ) ? $_MODE['w_start'] : $_MODE['w_end'] ;

	// open file (with lock?)
	if ( !$_handle = fopen($fpath, $_mode) )
	{
		trigger_error("unable to open file for writing [$fpath]", E_USER_WARNING);
		return 0;
	}
	
	// write
	if ( fwrite($_handle, $content) === FALSE )
	{
		trigger_error("unable to write to file [$fpath]", E_USER_WARNING);
		$is_written = 0;
	}
	else
	{
		$is_written = 1;
	}    
		
	fclose($_handle);
	return $is_written;
}
// END method


// method: print_r
function print_r($Mixed)
{
  $return = htmlspecialchars(print_r($Mixed, 1));
  $return = "<pre>$return</pre>";
  return $return;
}
// END method

// method: dump
function dump()
{
  echo $this->print_r($this);
  return;
}
// END method



/* ** PRIVATE METHODS ** */
// method: load db object
function _load_dbo()
{
  // already loaded
  if ( is_object($this->DBO) ) return;
  
  if ( $this->debug ) $this->print_d('loading db object');

  // load
  require_once($this->_dbo_driver);
  
  // settings
  $_flags = 2;
  $_dsn = "mysql://{$this->_dbu}:{$this->_dbp}@{$this->_dbs}/{$this->_dbn}?clientflags=$_flags";

  // create object
  if ( !$this->DBO =& ADONewConnection($_dsn) ) 
  {
    trigger_error('unable to connect to database ['.$db_name.']', E_USER_WARNING);
  }

  return;
}
// END method

// method: get hostname by ip
function _get_hostname_by_ip($ip)
{
  $hostname = '';
  $hostname = @getHostByAddr($ip);
  return $hostname;
}
// END method

// method: get hostname by ip
function _get_client_proxy_hostname($ip)
{
  $proxy_hostname = '';
  if ( empty($this->client_proxy) ) return '';
  $proxy_hostname = $this->_get_hostname_by_ip($ip);
  return $proxy_hostname;
}
// END method

// method: extract first ip from string
function _extract_first_ip($string) 
{
  $ip = '';
  $MATCH = array();
  $regex = '^([0-9]{1,3}\.){3,3}[0-9]{1,3}';
  
  if ( ereg($regex, $string, $_MATCH) )
  {
    $ip = $_MATCH[0];
  }
  
  return $ip;
} 
// END method

// method: is valid ip
function _is_valid_ip($ip) 
{
  $is_valid = 0;
  
  // reserved IPs
  $_RESERVED_IP = array
  (
    array('0.0.0.0','2.255.255.255'),
    array('10.0.0.0','10.255.255.255'),
    array('127.0.0.0','127.255.255.255'),
    array('169.254.0.0','169.254.255.255'),
    array('172.16.0.0','172.31.255.255'),
    array('192.0.2.0','192.0.2.255'),
    array('192.168.0.0','192.168.255.255'),
    array('255.255.255.0','255.255.255.255')
  );

  // sanity check
  if ( empty($ip) || ip2long($ip) == -1 ) return 0;

  // check for reserved IP
  foreach ( $_RESERVED_IP as $_LIMIT )
  {
    $_min = ip2long($_LIMIT[0]);
    $_max = ip2long($_LIMIT[1]);
  
    // check range
    if ( (ip2long($ip) >= $_min) && (ip2long($ip) <= $_max) ) return 0;
  }
  
  // still here?  must be valid
  $is_valid = 1;

  return $is_valid;
}
// END method

// method: _set_session_data
function _set_session_data()
{
  // initialize session
  if ( !session_id() ) session_start(); 
  $_SESSION[$this->oid] = array();
  
  // add session data here
  $_SESSION[$this->oid]['session_uid'] = $this->session_uid;
  $_SESSION[$this->oid]['baffleticket'] = $this->visitor_baffleticket;
  $_SESSION[$this->oid]['bafflestamp'] = time(); 
  
  return;
}
// END method

// method: get session data
function _get_session_data()
{
  // initialize session
  if ( !$this->_has_session_data() ) return; 
    
  // retrieve session variables
  $this->session_uid = $_SESSION[$this->oid]['session_uid'];
  
  return;
}
// END method

// method: has session data
function _has_session_data()
{
  // initialize session
  if ( !session_id() ) session_start(); 
    
  // retrieve session variables
  if ( empty($_SESSION[$this->oid]) ) return 0;
  else return 1;
}
// END method

function _set_filename() { $this->_filename = basename(__FILE__); }
function _set_dirpath() { $this->_dirpath = dirname(__FILE__) . $this->DS; }

} // end class
/*____________________________________________________________________________*/

?>
